#include <GL/gl.h>
#include <iostream>

#include <cmath>
#include <vector>
#include <algorithm>
#include "spherePrimitive.h"
#include "defines/primitive.h"
#include "defines/axes.h"
#include "defines/octreeType.h"

#define START_PHI 80
#define PHI 16
#define THETA 18

SpherePrimitive::SpherePrimitive(int _id) : Object(_id){
}

SpherePrimitive::SpherePrimitive(const Vector3D &_center, double _radius, int _id)
    : Object(_id, SPHERE, true), center(_center), radius(_radius){
}

SpherePrimitive::SpherePrimitive(const Vector3D &_center, double _radius, const Color &_c, int _id)
    : Object(_c, _id, SPHERE, true), center(_center), radius(_radius){
}

SpherePrimitive::~SpherePrimitive(){
}

Vector3D SpherePrimitive::getCenter() const{
    return center;
}

void SpherePrimitive::setCenter(const Vector3D &_c){
    center = _c;
}

double SpherePrimitive::getRadius() const{
    return radius;
}

void SpherePrimitive::setRadius(double _r){
    radius = _r;
}

double SpherePrimitive::volume() const{
    return (M_PI*radius*radius*radius*4.0)/3.0;
}

double SpherePrimitive::area() const{
    return (M_PI*radius*radius*4.0);
}

void SpherePrimitive::draw(bool _wireframe, bool _useParentColor, const Color &_parentColor) const{


    if(_wireframe){
        glDisable(GL_LIGHT0);
        glDisable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_CULL_FACE);

        glLineWidth(2);
    }

	glPushMatrix();
	glTranslated(center.x, center.y, center.z);
	glRotated(rotationX * 90, 1.0, 0.0, 0.0);
	glRotated(rotationY * 90, 0.0, 1.0, 0.0);
	glRotated(rotationZ * 90, 0.0, 0.0, 1.0);


    Color color_ = chooseColor(_wireframe, _useParentColor, _parentColor);
    glColor4d(color_.red, color_.green, color_.blue, color_.alpha);

    glBegin(GL_TRIANGLE_FAN);
    Vector3D normal = (Vector3D(0.0, 0.0, radius)).normalize();
    glNormal3f(normal.x, normal.y, normal.z);
    glVertex3f(0.0, 0.0, radius);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(i);
        double x = radius*cos(theta)*cos(toRad(START_PHI));
        double y = radius*sin(theta)*cos(toRad(START_PHI));
        double z = radius*sin(toRad(START_PHI));

        normal = (Vector3D(x, y, z)).normalize();
        glNormal3f(normal.x, normal.y, normal.z);
        glVertex3f(x, y, z);
    }
    glEnd();


    glBegin(GL_QUAD_STRIP);
    for(double i=START_PHI; i > -START_PHI; i = i-PHI){
        for(double j=0; j<=360; j=j+THETA){

            double theta = toRad(j);
            double phi = toRad(i);
            double x = radius*cos(theta)*cos(phi);
            double y = radius*sin(theta)*cos(phi);
            double z = radius*sin(phi);

            normal = (Vector3D(x, y, z)).normalize();
            glNormal3f(normal.x, normal.y, normal.z);
            glVertex3f(x, y, z);

            phi = toRad(i - PHI);
            x = radius*cos(theta)*cos(phi);
            y = radius*sin(theta)*cos(phi);
            z = radius*sin(phi);

            normal = (Vector3D(x, y, z)).normalize();
            glNormal3f(normal.x, normal.y, normal.z);
            glVertex3f(x, y, z);
        }
    }
    glEnd();

    glBegin(GL_TRIANGLE_FAN);
    normal = (Vector3D(0.0, 0.0, -radius)).normalize();
    glNormal3f(normal.x, normal.y, normal.z);
    glVertex3f(0.0, 0.0, -radius);

    for(double i=0; i<=360.0; i = i+THETA){
        double theta = toRad(-i);
        double x = radius*cos(theta)*cos(toRad(-START_PHI));
        double y = radius*sin(theta)*cos(toRad(-START_PHI));
        double z = radius*sin(toRad(-START_PHI));
        Vector3D normal = (Vector3D(x, y, z)).normalize();

        glNormal3f(normal.x, normal.y, normal.z);

        glVertex3f(x, y, z);
    }
    glEnd();



	glPopMatrix();

    if(_wireframe){
        glLineWidth(1);

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glEnable(GL_CULL_FACE);
    }

	glPushMatrix();
	glTranslated(center.x, center.y, center.z);
    if(selected){
        Vector3D min, max;
        getMinMax(&min.x, &max.x, X_AXIS);
        getMinMax(&min.y, &max.y, Y_AXIS);
        getMinMax(&min.z, &max.z, Z_AXIS);

        drawBox(true, min - center, max - center);
    }
	glPopMatrix();
}


int SpherePrimitive::classify(Vector3D *_octreePoint) const{
    //pegando o caso BLACK e alguns GREY
    bool allPointsOutside = true;
    bool allPointsInside = true;
    double radius_2 = radius * radius;

    for(int i = 0; i < 8; i++){
        if( (_octreePoint[i] - center).length_2() < radius_2 ){
            allPointsOutside = false;
        }else{
            allPointsInside = false;
        }
    }
    if(allPointsInside) return BLACK;   //todos os pontos dentros
    if(!allPointsOutside) return GREY;    //nem todos os pontos dentro (sedo algum do lado de fora)

    //pegando os casos WHITE
    Vector3D min = _octreePoint[0], max = _octreePoint[7];
    double minDistance_2 = 0; //a menor distancia entre o centro da esfera ao cubo ao quadrado

    if(center.x < min.x){
        minDistance_2 += (min.x - center.x) * (min.x - center.x);
    }else if(center.x > max.x){
        minDistance_2 += (max.x - center.x) * (max.x - center.x);
    }
    if(center.y < min.y){
        minDistance_2 += (min.y - center.y) * (min.y - center.y);
    }else if(center.y > max.y){
        minDistance_2 += (max.y - center.y) * (max.y - center.y);
    }
    if(center.z < min.z){
        minDistance_2 += (min.z - center.z) * (min.z - center.z);
    }else if(center.z > max.z){
        minDistance_2 += (max.z - center.z) * (max.z - center.z);
    }

    if(minDistance_2 >= radius_2) return WHITE;
    return GREY;
}

void SpherePrimitive::getMinMax(double *_min, double *_max, unsigned int _axis) const{
    switch(_axis){
        case X_AXIS:{
            *_min = center.x - radius;
            *_max = center.x + radius;
            break;
        }
        case Y_AXIS:{
            *_min = center.y - radius;
            *_max = center.y + radius;
            break;
        }
        case Z_AXIS:{
            *_min = center.z - radius;
            *_max = center.z + radius;
            break;
        }
    }
}
